home *** CD-ROM | disk | FTP | other *** search
Korn shell script | 1997-08-26 | 21.4 KB | 677 lines |
- #!/bin/ksh
- # @(#) maillist.ksh 1.4 97/02/19
- # 92/09/27 john h. dubois iii (john@armory.com)
- # 92/10/13 Added dellist capability.
- # 92/10/15 Check for whether user is already on list.
- # 92/10/17 Allow multiple users on cmd line.
- # 92/11/09 Added help.
- # 93/07/13 Do not create list- if no changes are made.
- # 93/07/17 Store lists with multiple addresses per line. Added -c option.
- # 94/01/10 Make default for LISTS be home dir, not current dir.
- # 94/04/05 Added A and R options.
- # 94/04/23 Use .maillist file.
- # 94/07/28 Let quoted patterns be used for mailing list names.
- # 94/08/15 Give better error messages if list does not exist or isn't readable.
- # 95/04/13 Make -[dD] ("delete") be the same as -[rR] ("remove")
- # 95/10/16 Added fi options. Don't require mailing list name if the user has
- # only one mailing list. Let % by a synonym for *. Improved help.
- # 96/01/26 Report full path to alias file on error.
- # Make list file be relative to current dir if it contains a /
- # 96/01/29 Let options be grouped together. Added [qs] options.
- # 96/02/16 Made modifiers work again; made c option work again.
- # 97/02/19 Ignore case in addresses.
-
- alias istrue="test 0 -ne"
- alias isfalse="test 0 -eq"
- rcfile=.maillist
-
- # Usage: ReadList <array> <listname>
- # The comma/whitespace separated words in file <listname> are read, sorted,
- # and put into array <array>
- function ReadList {
- typeset IFS="$IFS," arr=$1 list=$2
-
- if [ ! -f "$list" ]; then
- print -u2 "$lngname: $list: No such mailing list in $PWD."
- return 1
- fi
- if [ ! -r "$list" ]; then
- print -u2 "$lngname: Cannot read list datafile '$PWD/$list'."
- return 1
- fi
- set -s -A $arr -- $(<$list)
- }
-
- # Usage: IsOnList user-name
- # Returns success if user-name is in any element of global MList[]
- function IsOnList {
- typeset -i nelem=${#MList[*]} i=0
- typeset -l user=$1 listelem
-
- while [ i -lt nelem ]; do
- listelem=${MList[i]}
- [ "$user" = "$listelem" ] && return 0
- let i+=1
- done
- return 1
- }
-
- # Print words passed as args with max line length
- # Usage: PrintMaxLines <maxlinelength> <sep> <end> word ...
- # <sep> is used to separate words on a line.
- # <end> is put at the end of each line except the last.
- function PrintMaxLines {
- typeset maxline=$1 sep=$2 end=$3 outline= word
- typeset -i printedline=0 extra=${#sep}+${#end}
-
- shift 3
- for word; do
- if [[ $(( ${#outline}+${#word}+$extra )) -gt $maxline ]]; then
- isfalse printedline && printedline=1 || print -r "$end"
- print -nr "$outline"
- outline=$word
- else
- [ -z "$outline" ] && outline=$word || outline="$outline$sep$word"
- fi
- done
- # print final line if it hasn't been printed yet
- istrue printedline && print -r "$end"
- print -r "$outline"
- }
-
- # Usage: WriteList <listname>
- # Writes mailing list in MList[] to file <listname>
- function WriteList {
- cp "$list" "$list-"
- PrintMaxLines 79 ", " "," "${MList[@]}" > $list ||
- print -u2 "$lngname: List write failed."
- }
-
- # Usage: iInd <arrayname> <varname> <value> <nsearch> <firstelem>
- # Sets <varname> to the index of the first element of <arrayname>
- # that has value <value>. Case is ignored.
- # If the value is not found, <varname> is not set and 1 is returned;
- # if it is, 0 is returned.
- # If <nsearch> is given, the first <nsearch> elements of the array are
- # searched, with only nonempty elements counted.
- # If not, the first n nonempty elements are searched (up to 1024),
- # where n is the number of elements in the array.
- # If a fourth argument is given, it is the index to start with; the search
- # continues for <nsearch> elements.
- function iInd {
- typeset -i NElem ElemNum=${5:-0} NumNonNull=0
- typeset Arr=$1 var=$2
- typeset -l ElemVal Val=$3
-
- [ $# -ge 4 ] && NElem=$4 || eval NElem=\${#$Arr[*]}
- while [ ElemNum -le 1023 -a NumNonNull -lt NElem ]; do
- eval ElemVal=\"\${$Arr[ElemNum]}\"
- if [ "$Val" = "$ElemVal" ]; then
- eval $var=\$ElemNum
- return 0
- fi
- [ -n "$ElemVal" ] && let NumNonNull+=1
- let ElemNum+=1
- done
- return -1
- }
-
- # Usage: ChangeHosts listname old-host new-host
- # If old-host exists in listname, remove it and add new-addr to listname.
- # will need to copy much of DoUsers into this...
- #function ChangeList {
- # typeset list=$1 old new
- #
- # while [ $# -gt 1 ]; do
- # old=$2 new=$3
- # if DoUsers 0 "$list" "$old"; then
- # DoUsers 1 "$list" "$new"
- # else
- # $Quiet || print -u2 "$lngname: $new not added to list $list."
- # fi
- # shift 2
- # done
- #}
-
- # Usage: ChangeList listname old-addr new-addr [old-addr new-addr ...]
- # If old-addr exists in listname, remove it and add new-addr to listname.
- function ChangeList {
- typeset list=$1 old new
-
- while [ $# -gt 1 ]; do
- old=$2 new=$3
- if DoUsers 0 "$list" "$old"; then
- DoUsers 1 "$list" "$new"
- else
- $Quiet || print -u2 "$lngname: $new not added to list $list."
- fi
- shift 2
- done
- }
-
- # Usage: DoUsers 0|1 <listname> user ...
- # Use 0 to remove a user, 1 to add a user
- # Returns 0 on complete success, 1 if a user was already on a list to be
- # added to or not on a list to be remove from or if any other error occurs.
- function DoUsers {
- typeset -i add=$1
- typeset list=$2
- typeset OnList OffList User
- typeset -i OnInd=0 OffInd=0 UInd err=0
-
- shift 2
-
- ReadList MList $list || return 1
- for User; do
- if IsOnList "$User"; then
- OnList[OnInd]=$User
- let OnInd+=1
- else
- OffList[OffInd]=$User
- let OffInd+=1
- fi
- done
- if istrue add; then
- if [ OnInd -gt 0 ]; then
- echo "Warning: already on list \"$list\": ${OnList[*]}."
- err=1
- fi
- if [ OffInd -gt 0 ]; then
- set -s -A MList -- "${MList[@]}" "${OffList[@]}"
- WriteList $list
- echo "Added to list $list: ${OffList[*]}"
- fi
- else
- if [ OffInd -gt 0 ]; then
- $Quiet || echo "Warning: not on list \"$list\": ${OffList[*]}."
- err=1
- fi
- if [ OnInd -gt 0 ]; then
- for User in "${OnList[@]}"; do
- if iInd MList UInd "$User"; then
- unset MList[UInd]
- else
- print -u2 "$lngname: Failed to find index of $User."
- err=1
- fi
- done
- WriteList $list
- echo "Removed from list $list: ${OnList[*]}"
- fi
- fi
- return $err
- }
-
- # Usage: fix <listname>
- function fix {
- ReadList MList "$1" || return 1
- WriteList "$1"
- chmod a+r "$1"
- }
-
- # Usage: findPat <user-pattern> <listname>
- function findPat {
- typeset list=$1 found=false
- typeset -l userPat=$2 user
-
- ReadList MList "$list" || return 1
- for user in "${MList[@]}"; do
- if eval [[ '"$user"' = "*$userPat*" ]]; then
- $found || { print -n "$list:"; found=true; }
- print -n " $user"
- fi
- done
- $found && print ""
- return 0
- }
-
- # Usage: initialize <listname>
- function initialize {
- if [ ! -f "$1" ]; then
- touch -- "$1" || {
- print -u2 "Could not create list file '$1'".
- exit 1
- }
- chmod 744 "$1"
- print -u2 "Created list file '$1'."
- else
- print -u2 "Checking list file '$1'."
- fix "$1"
- fi
- }
-
- function printLists {
- [ numLists -gt 1 ] && print "$list:"
- print -- "$(<$list)"
- }
-
- function doArgs {
- typeset opt Users ListArr OFS add list Args OnlyOne Lists act modifier
- typeset lNames line
- typeset -i i j MinArgs
-
- while getopts hfipqaArRcdDslun opt; do
- case $opt in
- h)
- echo \
- "$lngname: Manipulate mailing lists.
- $lngname prints and modifies \"included\" mailing lists. An included
- mailing list is one which exists as a file containing a list of recipients
- which is read each time mail for the list is received. The other type of
- mailing list is an internal list; these mailing lists are specified in the
- MMDF alias files and become part of the MMDF database when dbmbuild(ADM) is
- run. Included mailing lists are typically used to allow automated mailing list
- management tools (like majordomo) to manipulate mailing lists, to allow mailing
- lists to be easily updated (since dbmbuild does not have to be run after each
- modification), and to allow users (who are not allowed to run dbmbuild) to
- maintain their own mailing lists. This utility is intended to facilitate the
- use of included mailing lists for the latter two purposes.
- Note that both internal and included mailing lists are only needed (and
- should only be used) when it is desirable for multiple users to be able to mail
- to a list address. If a list is to be created for use by only one user, a
- mailing list read by the user's mail sending utility should be used instead.
- See the tables(F) man page for a description of how to configure the MMDF
- system to use an included mailing list. This must be done by the system
- administrator, after the list file has been created. If the list is configured
- into MMDF first, any mail sent to it before the file exists or before the file
- is made accessible to the deliver daemon will result in an error, with mail
- sent to the system administrator. You should also ensure that all directories
- in the path up to the list file are publicly executable. That can be done with
- the -i or -f option. $lngname only modifies the contents mailing list files
- (adding and removing recipients); it does not do any MMDF configuration.
- For simplicity, $lngname maintains mailing lists in a file with the same
- name as that of the list. The file must already exist, unless -i is being
- used. Since it will be read by the deliver daemon, it must be readable and
- accessible by \"other\". You can initially create the file either by using the
- -i option, or with the commands:
- touch <listname>
- chmod a+r <listname>
- where <listname> is the name of the mailing list file.
- If the filename contains a '/' character, it is searched for relative to
- the current directory. If it does not, the file is searched for in the
- directory given by the environment variable \"MAILLISTS\". MAILLISTS can also
- be set by creating a file named $rcfile in your home directory and
- putting in it a line of the form:
- MAILLISTS=maillist-dir
- where maillist-dir is the mailing list directory. If MAILLISTS is not set
- either in the environment or in $rcfile, it defaults to your home directory.
- The MAILLISTS environment variable takes precedence over the value set in
- $rcfile.
- Mailing list files should contain addresses separated by commas and
- optional whitespace. The initial list of recipients can be put in the file
- manually, and it can later be edited manually, so long as this rule is
- observed. $lngname does not require that names be separated by commas, but the
- deliver daemon does. If the mailing list is initially created with names
- separated by whitespace only, $lngname -k should be run on the file before it
- is used, to put it in the format that the deliver daemon expects. Alternately,
- all operations on the file can be done through $lngname.
- When $lngname is run, a copy of the list being operated on as it currently
- exists is saved in listname- (a file with the same name as the list but with a
- '-' attached). Mailing list names that end in '-' are ignored, so * can be
- used in a directory that contains only mailing lists to specify all of them.
- It should not be used if the directory contains any non-dotfiles that are not
- mailing list files. Also, shell patterns (like *) can be used from any
- directory if quoted to protect them from initial expansion by the shell; they
- will be expanded in the mailing list directory. The character '%' can be used
- as a synonym for *, with the advantage that it does not have to be quoted.
- $Usage
- To make this program easier to use in cases where a user is to be added or
- removed from a number of mailing lists, or where a number of users are to be
- added/removed from a mailing list, the add and remove functions can each be
- given in one of two ways. The argument is a list of users or mailing lists
- separated by commas; further arguments are a list of users or mailing lists
- (whichever was not given as the first argument) separated by whitespace. If
- a pattern (like *) that will be expanded into a list of mailing list files is
- used, the first form should be used, since the pattern will expand to a list
- of mailing list files separated by whitespace. If a command line substitution
- that will put user names on the command line is used, the second form should be
- used for the same reason. If all parameters will be explicitly given on the
- command line, either form may be used.
- Exactly one of the following options must be given. If arguments are given
- after an option, a space must separate the option from the arguments.
- Options:
- -h: Print this help information.
- -f listname [listname ...]: Fix the mailing list files: read the files and
- rewrite them in the format the deliver daemon expects, and set their
- permissions.
- -i listname [listname ...]: Initialize mailing list files. Like -f, except
- that any files that do not exist are created (as empty files). If MAILLIST
- is set, the files are created in the directory it specifies; if not, the
- files are created in the user's home directory. If the directory does not
- exist, it will be created.
- -p [listname ...] Print the specified mailing lists. If not listnames are
- given, all lists are printed. If more than one mailing list is printed,
- its name is printed first.
- -q: When removing a user from lists, do not complain about lists the user was
- not on. When changing a user address, do not warn about lists the user
- was not added to.
- -[ar] user[,user...] listname [listname ...]
- -[AR] listname[,listname...] user [user ...]
- Add/remove the specified users to/from the mailing list(s). -d and -D are
- identical to -r and -R. If the letter 'u' or 'l' follows any of these
- options (e.g. -au or -al), then the list of users or mailing list names
- respectively is read from the standard input and should not be given on the
- command line. Multiple user or mailing list names may be given on input
- lines.
- -s user-pattern [listname ...]: Print list names that user-pattern
- occurs in, followed by the users who matched the pattern. user-pattern is
- an unanchored shell pattern. If no listnames are given, % is used.
- -n old-hostname new-hostname [listname ...]
- Change every matching hostname found in the recipient names on the given
- mailing lists to new-hostname. old-hostname must match the entire hostname
- part of an address. The only forms of address understood are user@hostname
- and hostname!user. (The hostname will actually also match certain parts of
- a UUCP or source route, since this option looks for a match on a hostname
- after the last @ in an address and before the first !, but this should not
- be relied upon). If no listnames are given, all lists are acted on.
- Note: -n is not yet implemented.
- -c current-address new-address [listname ...]
- Change the email address of the specified user on the mailing list(s).
- Only one user can be changed per invokation. The current subscription
- address should be given first, then the new subscription address. If
- current-address does not exist in a list, new-address is not added to it.
- For example, to change the subscription address of user@foo to user@bar
- on mylist, use: $lngname -c user@foo user@bar mylist
- If -cu is given instead of -c, pairs of addresses separated by whitespace
- are read from the standard input. For each line read, the first address
- given on the line is taken to be a current address and the second address
- is taken to be a new address and they are used to do a replacement as
- described above. In this case, all arguments are taken to be listnames.
- If no listnames are given, all lists are acted on.
- For the f, p, a, r, and c options only, if the user has exactly one mailing
- list, and MAILLISTS is set, and the mailing list file is the only file in the
- MAILLISTS directory, then the listname does not have to be given on the command
- line."
- exit 0
- ;;
- q)
- Quiet=true
- ;;
- [ardARDcnpsif])
- if [ -n "$act" ]; then
- print -u2 "$lngname: Multiple actions specified ($act"\
- " and $opt). Use -h for help. Aborting."
- exit 1
- fi
- act=$opt
- ;;
- [ul])
- if [ -n "$Mod" ]; then
- print -u2 "$lngname: Multiple modifiers specified ($Mod"\
- " and $opt). Use -h for help. Aborting."
- exit 1
- fi
- Mod=$opt
- ;;
- :)
- print -r -u2 -- \
- "$name: Option '$OPTARG' requires a value. Use -h for help."
- exit 1
- ;;
- ?)
- print -u2 "$lngname: $OPTARG: bad option. Use -h for help."
- exit 1
- ;;
- esac
- done
-
- # remove args that were options
- let OPTIND=OPTIND-1
- shift $OPTIND
-
- case "$act" in
- [ardARD]) # Adding or removing users
- ReadUsers=false
- ReadLists=false
- ReadInput=false
- case "$Mod" in
- u)
- ReadUsers=true
- ReadInput=true
- ArgsReq=list
- ArgsRead=user
- MinArgs=1
- ;;
- l)
- ReadLists=true
- ReadInput=true
- ArgsReq=user
- ArgsRead=list
- MinArgs=1
- ;;
- "")
- [[ $act = [ARD] ]] &&
- ArgsReq="list and user" || ArgsReq="user and list"
- MinArgs=2
- ;;
- esac
- if [ $# -lt MinArgs ]; then
- print -u2 \
- "$lngname: must give $ArgsReq names(s) with -$act. Use -h for help."
- exit 1
- fi
- OFS=$IFS
- IFS=,
- if $ReadInput; then
- [ -t 0 -a -t 1 ] && print "Enter $ArgsRead names; end with ^D"
- set -A ReadArgs $(<&0)
- if [ "${#ReadArgs[*]}" -eq 0 ]; then
- print -u2 \
- "$lngname: must give at least one $ArgsRead name. Use -h for help."
- exit 1
- fi
- fi
- if [[ $act = [ard] ]]; then # arg1 is users, others are lists
- $ReadUsers || {
- Users=$1
- shift
- }
- if $ReadLists && [ $# -gt 1 ]; then
- print -u2 "Too many arguments given with -$act$Mod"
- exit 1
- fi
- set -A ListArr "$@"
- if $ReadUsers; then
- set -A Args -- "${ReadArgs[@]}"
- else
- set -A Args $Users
- fi
- else # arg1 is lists, others are users
- $ReadLists || {
- if [ $# -eq 0 ]; then
- print -u2 \
- "$lngname: must give list names(s) with -$act. Use -h for help."
- exit 1
- fi
- ListList=$1
- shift
- }
- if $ReadUsers && [ $# -gt 1 ]; then
- print -u2 "Too many arguments given with -$act$Mod"
- exit 1
- fi
- set -A Args "$@"
- if $ReadLists; then
- set -A ListArr -- "${ReadArgs[@]}"
- else
- set -A ListArr $ListList
- fi
- fi
- IFS=$OFS
- # Set arg to DoUser to be 0 if removing, 1 if adding
- [[ $act = [rRdD] ]]
- ListCmd="DoUsers $?"
- set -- "${ListArr[@]}"
- ;;
- n)
- ListCmd=ChangeHosts
- if [ $# -lt 2 ]; then
- print -u2 \
- "$lngname: must give old & new host names with -$act. Use -h for help."
- exit 1
- fi
- Args[1]=$1
- Args[2]=$2
- shift 2
- [ $# -eq 0 ] && set -- %
- ;;
- c)
- ListCmd=ChangeList
- case "$Mod" in
- u)
- # Save args, since any set -A overwrites them
- set -A lNames -- "$@"
- [ -t 0 -a -t 1 ] &&
- print "Enter pairs of user names; end with ^D"
- while read line; do
- set -- $line
- case $# in
- 0)
- ;;
- 2)
- set -A Args "${Args[@]}" "$1" "$2"
- ;;
- *)
- print -u2 \
- "$lngname: Need exactly two names on each line. Ignored: $line"
- ;;
- esac
- done
- set -- "${lNames[@]}"
- ;;
- "")
- if [ $# -lt 2 ]; then
- print -u2 \
- "$lngname: must give -u or old & new user names with -$act. Use -h for help."
- exit 1
- fi
- Args[1]=$1
- Args[2]=$2
- shift 2
- ;;
- *)
- print -u2 \
- "$lngname: Invalid modifier '$Mod' given with -$act. Use -h for help."
- exit 1
- ;;
- esac
- [ $# -eq 0 ] && set -- %
- ;;
- s)
- if [ $# -lt 1 ]; then
- print -u2 \
- "$lngname: must give user pattern -$act. Use -h for help."
- exit 1
- fi
- Args[1]=$1
- shift
- ListCmd=findPat
- [ $# -eq 0 ] && set -- %
- ;;
- p)
- ListCmd=printLists
- [ $# -eq 0 ] && set -- %
- ;;
- i)
- ListCmd=initialize
- ;;
- f)
- ListCmd=fix
- ;;
- esac
- # List dir might not exist if doing initialize.
- # But if the dir does exist, should cd to it even if doing initialize
- # before expanding list names.
- if [ ! -d "$MAILLISTS" ]; then
- if [ $ListCmd = initialize ]; then
- print "Making list directory '$MAILLISTS'."
- mkdir -e -p -m 711 "$MAILLISTS" || {
- print -u2 "Could not create mailing list directory '$MAILLISTS'"
- exit 1
- }
- else
- print -u2 \
- "Mailing list directory '$MAILLISTS' does not exist or is not a directory."
- exit 1
- fi
- fi
- OPWD=${PWD%/}
- cd -- "$MAILLISTS" || {
- print -u2 "Could not cd to mailing list directory '$MAILLISTS'."
- exit 1
- }
- # At this point, positional arguments are list names.
-
- if [[ $# -eq 0 && "$opt" = [fpadrc] && $NoML = false ]]; then
- set -- *
- OnlyOne=true
- else
- OnlyOne=false
- set -A Lists -- "$@"
- typeset -i j=0 i=${#Lists[*]}
- while [ j -lt i ]; do
- case "${Lists[j]}" in
- %) Lists[j]='*';;
- */*) Lists[j]="$OPWD/${Lists[j]#./}";;
- esac
- let j+=1
- done
-
- # Expand filename patterns
- eval set -- "${Lists[@]}"
- fi
-
- unset Lists[*]
- i=0
- for list; do
- if [[ "$list" != *- ]]; then
- Lists[i]=$list
- let i+=1
- fi
- done
- if [ i -eq 0 ]; then
- print -u2 "$lngname: No lists."
- exit 1
- elif $OnlyOne; then
- if [ i -eq 1 ]; then
- print "Acting on mailing list ${Lists[*]}"
- else
- print -u2 "$lngname: No list names given on command line,
- and too many list files in MAILLISTS directory."
- exit 1
- fi
- fi
- typeset -i numLists=${#Lists[@]} # for use by printLists
- for list in "${Lists[@]}"; do
- $ListCmd "$list" "${Args[@]}" || {
- [[ "$list" != /* ]] && list="$PWD/$list"
- print -u2 "Failure occured in processing $list"
- }
- done
- }
-
- ### Start of main program
-
- NoML=false
- Quiet=false
-
- if [ -z "$MAILLISTS" ]; then
- rc=$HOME/$rcfile
- [ -r $rc -a -f $rc ] && . $rc
- if [ -z "$MAILLISTS" ]; then
- MAILLISTS=$HOME
- NoML=true
- fi
- fi
-
- lngname=${0##*/}
-
- Usage="Usage: $lngname [-aAcfhilpqrsRu] arg[,arg...] arg ..."
-
- if [[ $# -eq 0 || "$1" != -?* ]]; then
- print -u2 "$Usage\nUse -h for help."
- exit
- fi
-
- doArgs "$@"
-